﻿using System;
using System.Collections;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ResourceReportBuilder.Properties;

namespace ResourceReportBuilder
{
    public enum BrowseType
    {
        Details = 0,
        Summary = 1
    }

    public delegate void FileSystemDetailEventHandler(object sender, FileSystemDetailEventArgs e);

    public partial class FileBrowser : UserControl
    {
        private IntPtr hImg;
        private ResourceReportBuilder.Win32.SHFILEINFO shinfo = new ResourceReportBuilder.Win32.SHFILEINFO();
        private ListViewItem[] itemCache; //array to cache items for the virtual list
        private int firstItem; //stores the index of the first item in the cache
        private Hashtable _historySelection;
        private int _level = 0;

        public event ItemDragEventHandler ItemDrag;
        public event FileSystemDetailEventHandler FileDoubleClick;
        public event FileSystemDetailEventHandler FolderDoubleClick;

        public ListView.SelectedListViewItemCollection SelectedItems { get { return fileList.SelectedItems; } }

        private bool _showDrives = true;
        #region public bool ShowDrives
        /// <summary>
        /// Get/Sets the ShowDrives of the FileBrowser
        /// </summary>
        /// <value></value>
        public bool ShowDrives
        {
            get { return _showDrives; }
            set
            {
                _showDrives = value;
                drivePanel.Visible = value;
                fileList.Top = value ? 38 : 0;
                fileList.Height = value ? this.Height - 38 : this.Height;
            }
        }
        #endregion

        private string _drive;
        #region public string Drive
        /// <summary>
        /// Get/Sets the Drive of the FileBrowser
        /// </summary>
        /// <value></value>
        public string Drive
        {
            get { return _drive; }
            set { _drive = value; }
        }
        #endregion

        private string _rootPath;
        #region public string RootPath
        /// <summary>
        /// Get/Sets the RootPath of the FileBrowser
        /// </summary>
        /// <value></value>
        public string RootPath
        {
            get { return _rootPath; }
            internal set { _rootPath = value; }
        }
        #endregion

        private BrowseType _browserType;
        #region public BrowseType BrowserType
        /// <summary>
        /// Get/Sets the BrowserType of the FileBrowser
        /// </summary>
        /// <value></value>
        public BrowseType BrowserType
        {
            get { return _browserType; }
            set { _browserType = value; }
        }
        #endregion

        #region public FileBrowser()
        /// <summary>
        /// Initializes a new instance of the <b>FileBrowser</b> class.
        /// </summary>
        public FileBrowser()
        {
            InitializeComponent();
            if (!DesignMode)
            {
                _historySelection = new Hashtable();
                iconList16.Images.Add("default", Resources.empty16);
                iconList32.Images.Add("default", Resources.empty32);
                CreateDiskButtons();
                GetDirContent("c:\\", false, false);
                fileList.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(fileList_RetrieveVirtualItem);
                fileList.CacheVirtualItems += new CacheVirtualItemsEventHandler(fileList_CacheVirtualItems);
                fileList.Activation = ItemActivation.OneClick;
            }
        }
        #endregion

        #region virtualmode
        void fileList_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
        {
            //We've gotten a request to refresh the cache.
            //First check if it's really neccesary.
            if (itemCache != null && e.StartIndex >= firstItem && e.EndIndex <= firstItem + itemCache.Length)
            {
                //If the newly requested cache is a subset of the old cache, 
                //no need to rebuild everything, so do nothing.
                return;
            }

            //Now we need to rebuild the cache.
            firstItem = e.StartIndex;
            int length = e.EndIndex - e.StartIndex + 1; //indexes are inclusive
            itemCache = new ListViewItem[length];

            //Fill the cache with the appropriate ListViewItems.
            int x = 0;
            for (int i = 0; i < length; i++)
            {
                x = (i + firstItem) * (i + firstItem);
                itemCache[i] = new ListViewItem(x.ToString());
            }
        }

        void fileList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            //Caching is not required but improves performance on large sets.
            //To leave out caching, don't connect the CacheVirtualItems event 
            //and make sure myCache is null.

            //check to see if the requested item is currently in the cache
            if (itemCache != null && e.ItemIndex >= firstItem && e.ItemIndex < firstItem + itemCache.Length)
            {
                //A cache hit, so get the ListViewItem from the cache instead of making a new one.
                e.Item = itemCache[e.ItemIndex - firstItem];
            }
            else
            {
                //A cache miss, so create a new ListViewItem and pass it back.
                int x = e.ItemIndex * e.ItemIndex;

                //TODO
                e.Item = new ListViewItem(x.ToString());
            }

        }
        #endregion

        #region private void CreateDiskButtons()
        /// <summary>
        /// 
        /// </summary>
        private void CreateDiskButtons()
        {
            DriveInfo[] allDrives = DriveInfo.GetDrives();
            foreach (DriveInfo di in allDrives)
            {
                RadioButton driveButton = new RadioButton();
                driveButton.Appearance = Appearance.Button;
                driveButton.Width = 40;
                driveButton.Text = di.Name.ToLower().Substring(0, di.Name.Length - 1);
                driveButton.Tag = di.Name;
                driveButton.TextAlign = ContentAlignment.MiddleRight;
                driveButton.ImageAlign = ContentAlignment.MiddleLeft;
                driveButton.TabStop = false;
                Icon ico = FileUtils.GetAssociatedIcon(di.Name);
                if (ico != null)
                {
                    driveButton.Image = FileUtils.GetAssociatedIcon(di.Name).ToBitmap();
                }
                driveButton.Click += new EventHandler(driveButton_Click);
                drivePanel.Controls.Add(driveButton);
            }
        }
        #endregion

        #region void driveButton_Click(object sender, EventArgs e)
        /// <summary>
        /// This method is called when the driveButton's Click event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> of the event.</param>
        void driveButton_Click(object sender, EventArgs e)
        {
            RadioButton driveButton = sender as RadioButton;
            GetDirContent(driveButton.Tag as string, false, true);
            if (fileList.Items.Count > 0)
            {
                for (int i = 0; i < fileList.SelectedIndices.Count; i++)
                {
                    fileList.Items[i].Selected = false;
                }

                fileList.Items[0].Selected = true;
                fileList.FocusedItem = fileList.Items[0];
            }
            fileList.Select();
            _level = 0;
        }
        #endregion

        public void JumpTo(string path)
        {
            GetDirContent(path, false, false);
        }

        #region private void GetDirContent(string path, bool hasRootDir)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="hasRootDir"></param>
        private void GetDirContent(string path, bool hasRootDir, bool driveChange)
        {
            fileList.Columns[0].Text = GetMaxWidthPath(path, fileList.Columns[0].Width, fileList.Font);
            if (!Directory.Exists(path))
            {
                if (!File.Exists(path))
                {
                    fileList.Items.Clear();
                }
                return;
            }
            _historySelection[_level] = fileList.SelectedIndices.Count > 0 ? fileList.SelectedIndices[0] : 0;
            fileList.Items.Clear();
            if (!FileUtils.IsRoot(path))
            {
                AddContent(new DirectoryInfo(path), "..").Selected = true;
            }

            try
            {
                foreach (string dir in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
                {
                    AddContent(new DirectoryInfo(path), dir.Substring(path.Length));
                }
                foreach (string file in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
                {
                    AddContent(new DirectoryInfo(path), file.Substring(path.Length));
                }
            }
            catch
            {

            }

            bool forward = path.Contains("..");
            if (hasRootDir)
            {
                _level += forward ? -1 : 1;
            }
            else
            {
                _level = 0;
            }
            if (!driveChange)
            {
                int? historyIndex = (int?)_historySelection[_level];
                if (historyIndex.HasValue && historyIndex.Value < fileList.Items.Count)
                {
                    fileList.Items[historyIndex.Value].Selected = true;
                    fileList.FocusedItem = fileList.Items[historyIndex.Value];
                }
                else if (forward)
                {
                    fileList.Items[0].Selected = true;
                    fileList.FocusedItem = fileList.Items[0];
                }
            }
            else
            {
                fileList.Items[0].Selected = true;
                fileList.FocusedItem = fileList.Items[0];
            }
            _historySelection[_level] = null;
        }
        #endregion

        private static string GetMaxWidthPath(string path, int width, Font font)
        {
            System.Drawing.Size sz = TextRenderer.MeasureText(path, font);
            if (sz.Width + 2 <= width)
            {
                return path;
            }
            string[] dirs = path.Split('\\');
            if (dirs.Length < 2)
            {
                return path;
            }
            string result = string.Empty;
            string oldResult = string.Empty;
            string rightVal = string.Empty;
            string leftVal = string.Empty;
            int separatorWidth = TextRenderer.MeasureText("...\\", font).Width + 2;
            for (int i = 0; i < dirs.Length / 2; i++)
            {
                oldResult = rightVal;
                rightVal = System.IO.Path.Combine(dirs.ToList().Reverse<string>().Where((item, index) => index <= i).Reverse<string>().ToArray());
                if (TextRenderer.MeasureText(rightVal, font).Width + separatorWidth >= width)
                {
                    return oldResult;
                }
                rightVal = System.IO.Path.Combine("...", rightVal);
                leftVal = string.IsNullOrWhiteSpace(leftVal) ? dirs[i] : string.Format("{0}\\{1}", leftVal, dirs[i]);
                oldResult = result;
                result = string.Format("{0}\\{1}", leftVal, rightVal);
                if (TextRenderer.MeasureText(result, font).Width + 2 >= width)
                {
                    return oldResult;
                }
            }
            return path;
        }

        #region public ListViewItem AddContent(DirectoryInfo rootDirectory, string path)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rootDirectory"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public ListViewItem AddContent(DirectoryInfo rootDirectory, string path)
        {
            while (path.StartsWith("\\"))
            {
                path = path.Substring(1);
            }
            ListViewItem item = new ListViewItem(path);
            item.ImageKey = GetAssociatedIconKey(Path.Combine(rootDirectory.FullName, path));
            _rootPath = rootDirectory.FullName;
            return fileList.Items.Add(item);
        }
        #endregion

        #region private string GetAssociatedIconKey(string path)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetAssociatedIconKey(string path)
        {
            string imageKey = "default";
            if (File.Exists(path) || Directory.Exists(path))
            {
                bool useSmallImages = _browserType == BrowseType.Details;

                if (useSmallImages)
                {
                    hImg = Win32.SHGetFileInfo(path, 0, ref shinfo,
                                  (uint)Marshal.SizeOf(shinfo),
                                   Win32.SHGFI_ICON |
                                   Win32.SHGFI_SMALLICON);
                }
                else
                {
                    hImg = Win32.SHGetFileInfo(path, 0, ref shinfo,
                                  (uint)Marshal.SizeOf(shinfo),
                                   Win32.SHGFI_ICON |
                                   Win32.SHGFI_LARGEICON);
                }
                Icon fileIcon = null;
                if (shinfo.hIcon != IntPtr.Zero)
                {
                    fileIcon = Icon.FromHandle(shinfo.hIcon);
                }

                if (fileIcon != null)
                {
                    imageKey = shinfo.iIcon.ToString();
                    bool canAdd = true;
                    foreach (ListViewItem itm in fileList.Items)
                    {
                        if (itm.ImageKey == imageKey)
                        {
                            canAdd = false;
                            break;
                        }
                    }
                    if (canAdd)
                    {
                        if (useSmallImages)
                        {
                            fileList.SmallImageList.Images.Add(imageKey, fileIcon);
                        }
                        else
                        {
                            fileList.LargeImageList.Images.Add(imageKey, fileIcon);
                        }
                    }
                }
            }
            else
            {
                imageKey = string.Empty;
            }
            return imageKey;
        }
        #endregion

        #region private void fileList_DoubleClick(object sender, EventArgs e)
        /// <summary>
        /// This method is called when the fileList's DoubleClick event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> of the event.</param>
        private void fileList_DoubleClick(object sender, EventArgs e)
        {
            if (fileList.SelectedItems.Count > 0)
            {
                string path = Path.Combine(_rootPath, fileList.SelectedItems[0].Text);
                GetDirContent(path, true, false);
                if (File.Exists(path))
                {
                    OnFileDoubleClick(path);
                }
                else if (Directory.Exists(path))
                {
                    OnFolderDoubleClick(path);
                }
            }
        }
        #endregion

        #region private void fileList_KeyDown(object sender, KeyEventArgs e)
        /// <summary>
        /// This method is called when the fileList's KeyDown event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="KeyEventArgs"/> of the event.</param>
        private void fileList_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Return && fileList.SelectedItems.Count > 0)
            {
                GetDirContent(Path.Combine(_rootPath, fileList.SelectedItems[0].Text), true, false);
            }
            if (e.KeyCode == Keys.Back && _level > 0 && fileList.Items.Count > 0)
            {
                GetDirContent(Path.Combine(_rootPath, fileList.Items[0].Text), true, false);
            }
        }
        #endregion

        private void fileList_ItemDrag(object sender, ItemDragEventArgs e)
        {
            OnItemDrag(e);
        }

        public virtual void OnItemDrag(ItemDragEventArgs e)
        {
            if (ItemDrag != null)
            {
                ItemDrag(this, e);
            }
        }

        public virtual void OnFileDoubleClick(string path)
        {
            if (FileDoubleClick != null)
            {
                FileDoubleClick(this, new FileSystemDetailEventArgs(path));
            }
        }

        public virtual void OnFolderDoubleClick(string path)
        {
            if (FolderDoubleClick != null)
            {
                FolderDoubleClick(this, new FileSystemDetailEventArgs(path));
            }
        }
    }
}
